package org.zoomdev.zoom.ioc.impl.builder;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.config.ConfigReader;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.filter.Filter;
import org.zoomdev.zoom.common.filter.impl.OrFilter;
import org.zoomdev.zoom.common.filter.pattern.PatternFilterFactory;
import org.zoomdev.zoom.common.res.ResScanner;
import org.zoomdev.zoom.common.res.impl.ResScannerImpl;
import org.zoomdev.zoom.common.utils.OrderedList;
import org.zoomdev.zoom.ioc.Ioc;
import org.zoomdev.zoom.ioc.IocKey;
import org.zoomdev.zoom.ioc.IocTypeKey;
import org.zoomdev.zoom.ioc.annotations.Module;
import org.zoomdev.zoom.ioc.impl.ZoomIoc;
import org.zoomdev.zoom.ioc.impl.factories.ModuleIocFactory;
import org.zoomdev.zoom.ioc.modules.CoreIocModule;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public final class IocBuilder {
    private static final Logger log = LoggerFactory.getLogger(CoreIocModule.NAME);
    public static boolean showFoundModuleLogs = true;
    final Ioc ioc = new ZoomIoc();
    private final Map<IocKey, Object> values = new HashMap<>();
    Filter<Class> moduleFilter = new AllFilter();
    ModuleIocFactory moduleIocFactory;
    private ClassLoader classLoader;
    private Filter<File> jarFilter = ResScannerImpl.fastFilter;
    private String config = "application";
    private String moduleScanFilter;
    private String scanFilter;
    private IocCreateParameters parameters;

    public IocBuilder() {
        this.classLoader = this.getClass().getClassLoader();
    }

    public static Ioc buildDefault() {
        return new IocBuilder().build();
    }

    public IocBuilder setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
        return this;
    }

    public IocBuilder createParameters(Map<String, String> data) {
        this.parameters = new IocCreateParameters(data);
        return this;
    }

    /**
     * 设置配置文件名称,默认application.properties
     *
     * @param config
     * @return
     */
    public IocBuilder setConfig(String config) {
        assert (config != null);
        this.config = config;
        return this;
    }

    /**
     * 直接设置模块的Class,白名单模式
     *
     * @param moduleClasses
     * @return
     */
    public IocBuilder setModules(Class... moduleClasses) {
        this.moduleFilter = moduleClasses.length == 0 ? new AllFilter() : new ModuleClassFilter(moduleClasses);
        return this;
    }

    public IocBuilder setModuleScanFilter(String moduleScanFilter) {
        this.moduleScanFilter = moduleScanFilter;
        return this;
    }

    /**
     * 设置需要排除的Class，黑名单模式
     *
     * @param moduleClasses
     * @return
     */
    public IocBuilder setExceptModules(Class... moduleClasses) {
        moduleFilter = moduleClasses.length == 0 ? new AllFilter() : new ExceptClassFilter(moduleClasses);

        return this;
    }

    public IocBuilder setJarFilter(String pattern) {
        this.jarFilter = new OrFilter<>(ResScannerImpl.fastFilter,
                new JarFilter(PatternFilterFactory.createFilter(pattern)));
        return this;
    }

    public IocBuilder put(IocKey key, Object value) {
        values.put(key, value);
        return this;
    }

    public Ioc build() {

        ioc.put(new IocTypeKey(IocCreateParameters.class), parameters);

        /**
         * 获取配置文件名称
         */
        String configName = parameters != null ? parameters.get("zoom.config.name") : null;

        //启用默认配置

        if (!StringUtils.isEmpty(configName)) {
            setConfig(configName);
        }

        /**
         * 配置模块过滤
         */
        String moduleFilter = parameters != null ? parameters.get("zoom.scan.modules") : null;
        if (!StringUtils.isEmpty(moduleFilter)) {
            setModuleScanFilter(moduleFilter);
        }

        //
        //        /**
        //         * 配置controller过滤
        //         */
        String scanFilter = parameters != null ? parameters.get("zoom.scan.filter") : null;
        if (!StringUtils.isEmpty(scanFilter)) {
            setScanFilter(scanFilter);
        }


        moduleIocFactory = new ModuleIocFactory(ioc);
        ioc.insertFactory(moduleIocFactory);


        for (Map.Entry<IocKey, Object> entry : values.entrySet()) {
            ioc.put(entry.getKey(), entry.getValue());
        }

        /**
         * 加载app配置文件
         */
        ConfigReader reader = ConfigReader.getDefault();
        ioc.put(new IocTypeKey(ConfigReader.class), reader);
        loadApplicationConfig(reader);

        Object showLogs = reader.get("zoom.ioc.showFoundModuleLogs");
        if (showLogs != null) {
            IocBuilder.showFoundModuleLogs = Caster.to(showLogs, Boolean.class);
        }

        /**
         * 扫描全目录
         */
        ResScanner scanner = new ResScannerImpl();
        ioc.put(new IocTypeKey(ResScanner.class), scanner);
        try {
            scanResources(scanner);
        } catch (IOException e) {
            throw new ZoomException("扫描资源失败", e);
        }


        /**
         * 执行模块初始化
         *
         */
        List<Class<?>> moduleClasses = findModules(reader, scanner);
        moduleIocFactory.parseModules(moduleClasses);

        return ioc;
    }

    public IocBuilder setScanFilter(String value) {
        this.scanFilter = value;
        return this;
    }

    private void loadApplicationConfig(ConfigReader reader) {
        // 加载全局配置
        if (!StringUtils.isEmpty(config)) {
            try {
                reader.load(config);
            } catch (IOException e) {
                log.warn("未找到配置文件: " + config + " , 将以默认配置运行");
            }


            String scanFilter = reader.getString("zoom.scan.jars");
            if (!StringUtils.isEmpty(scanFilter)) {
                setJarFilter(scanFilter);
            }
        } else {
            log.warn("====================未设置配置文件====================");
        }
    }

    private void scanResources(ResScanner scanner) throws IOException {
        if (scanFilter != null) {
            scanner.setFilter(scanFilter);
        }

        scanner.scan(classLoader, jarFilter);
    }

    int getOrder(Class<?> type) {
        String className = type.getName();
        if (className.startsWith("org.zoomdev.zoom")) {
            String simpleName = type.getSimpleName();
            if (simpleName.startsWith("Core")) {
                return 0;
            } else if (simpleName.startsWith("Plugin")) {
                return 1;
            } else if (simpleName.startsWith("System")) {
                return 500;
            } else {
                return 2;
            }
        } else if (className.contains("$")) {
            return 200;
        } else {
            return 100;
        }
    }

    List<Class<?>> findModules(ConfigReader reader, ResScanner scanner) {

        String pattern = this.moduleScanFilter;
        if (StringUtils.isEmpty(pattern)) {
            pattern = reader.getString("zoom.scan.modules");
        }

        if (StringUtils.isEmpty(pattern)) {
            pattern = "*.modules.*|*Module";
        } else {
            if (pattern.endsWith("|") || pattern.endsWith("&")) {
                pattern = pattern + "(*.modules.*|*Module)";
            } else {
                pattern = pattern + "&(*.modules.*|*Module)";
            }
        }

        List<ResScanner.ClassRes> classes = scanner.findClass(pattern);
        OrderedList<Class<?>> orderedList = new OrderedList<>();
        for (ResScanner.ClassRes res : classes) {
            Class<?> type = res.getType();
            if (!moduleFilter.accept(type)) {
                continue;
            }
            if (type.isAnnotationPresent(Module.class)) {
                orderedList.add(type, getOrder(type));
            }
        }

        List<Class<?>> ret = orderedList.toList();

        if (IocBuilder.showFoundModuleLogs) {
            for (Class<?> type : ret) {
                log.info("Found Module:" + type.getName());
            }
        }


        return ret;
    }

    static class AllFilter implements Filter<Class> {

        @Override
        public boolean accept(Class value) {
            return true;
        }
    }

    static class ModuleClassFilter implements Filter<Class> {

        Set<Class> sets = new HashSet<Class>();

        public ModuleClassFilter(Class... moduleClasses) {
            Collections.addAll(sets, moduleClasses);
        }

        @Override
        public boolean accept(Class value) {
            return sets.contains(value);
        }
    }

    static class ExceptClassFilter implements Filter<Class> {
        Set<Class> sets = new HashSet<Class>();

        public ExceptClassFilter(Class... moduleClasses) {
            Collections.addAll(sets, moduleClasses);
        }

        @Override
        public boolean accept(Class value) {
            return !sets.contains(value);
        }
    }


    private class JarFilter implements Filter<File> {

        private final Filter<String> filter;

        public JarFilter(Filter<String> filter) {
            this.filter = filter;
        }

        @Override
        public boolean accept(File value) {
            return filter.accept(value.getName());
        }

    }


}
